<?php
/* 
    Name：FastPHP
    CopyRight: Mr.Fu 2019SR0832915
*/
namespace app\extend\unionpay;
/*
	银联支付接口插件 -证书
*/
class Cert
{
    public $cert;
    public $certId;
    public $key;
}

class CertUtil
{
    private static $signCerts = array();
    private static $encryptCerts = array();
    private static $verifyCerts = array();
    private static $verifyCerts510 = array();
    
    private static function initSignCert($certPath, $certPwd){
		
        $pkcs12certdata = file_get_contents ( $certPath );
        if($pkcs12certdata === false ){
        	WLog('unionpay_log',$certPath . "file_get_contents fail。");
        	return false;
        }
        
        if(openssl_pkcs12_read ( $pkcs12certdata, $certs, $certPwd ) == FALSE ){
        	WLog('unionpay_log',$certPath . ", pwd[" . $certPwd . "] openssl_pkcs12_read fail。");
        	return false;
        }
        
        $cert = new Cert();
        $x509data = $certs ['cert'];

        if(!openssl_x509_read ( $x509data )){
        	WLog('unionpay_log',$certPath . " openssl_x509_read fail。");
        }
        $certdata = openssl_x509_parse ( $x509data );
        $cert->certId = $certdata ['serialNumber'];
        
        
        $cert->key = $certs ['pkey'];
        $cert->cert = $x509data;
        
		WLog('unionpay_log',"签名证书读取成功，序列号：" . $cert->certId);
		
        CertUtil::$signCerts[$certPath] = $cert;
    }
    
    public static function getSignKeyFromPfx($certPath=null, $certPwd=null)
    {   
        if (!array_key_exists($certPath, CertUtil::$signCerts)) {
            self::initSignCert($certPath, $certPwd);
        }
        return CertUtil::$signCerts[$certPath] -> key;
    } 
   	
    public static function getSignCertIdFromPfx($certPath=null, $certPwd=null)
    {
        if (!array_key_exists($certPath, CertUtil::$signCerts)) {
            self::initSignCert($certPath, $certPwd);
        }
        return CertUtil::$signCerts[$certPath] -> certId;
    }
    private static function initEncryptCert($cert_path)
    {

	    $x509data = file_get_contents ( $cert_path );
        if($x509data === false ){
        	WLog('unionpay_log',$cert_path . " file_get_contents fail。");
        	return false;
        }
	    
	    if(!openssl_x509_read ( $x509data )){
        	WLog('unionpay_log',$cert_path . " openssl_x509_read fail。");
        	return false;
	    }

	    $cert = new Cert();
	    $certdata = openssl_x509_parse ( $x509data );
	    $cert->certId = $certdata ['serialNumber'];

        $cert->key = $x509data;
        CertUtil::$encryptCerts[$cert_path] = $cert;
        
        WLog('unionpay_log',"加密证书读取成功，序列号：" . $cert->certId);
    }   
    
    public static function verifyAndGetVerifyCert($certBase64String,$Config){
    	
    	
    	if (array_key_exists($certBase64String, CertUtil::$verifyCerts510)){
    		return CertUtil::$verifyCerts510[$certBase64String];
    	}
    	
		if ($Config['middleCertPath'] === null || $Config['rootCertPath'] === null){
			WLog('unionpay_log',"rootCertPath or middleCertPath is none, exit initRootCert");
			return false;
		}
		openssl_x509_read($certBase64String);
		$certInfo = openssl_x509_parse($certBase64String);
		
		$cn = CertUtil::getIdentitiesFromCertficate($certInfo);
		if(strtolower($Config['ifValidateCNName']) == "true"){
			if ("中国银联股份有限公司" != $cn){
				WLog('unionpay_log',"cer owner is not CUP:" . $cn);
				return false;
			}
		} else if ("中国银联股份有限公司" != $cn && "00040000:SIGN" != $cn){
			WLog('unionpay_log',"cer owner is not CUP:" . $cn);
			return false;
		}
		
		$from = date_create ( '@' . $certInfo ['validFrom_time_t'] );
		$to = date_create ( '@' . $certInfo ['validTo_time_t'] );
		$now = date_create ( date ( 'Ymd' ) );
		$interval1 = $from->diff ( $now );
		$interval2 = $now->diff ( $to );
		if ($interval1->invert || $interval2->invert) {
			WLog('unionpay_log',"signPubKeyCert has expired");
			return false;
		}
		 
		$result = openssl_x509_checkpurpose($certBase64String, X509_PURPOSE_ANY, array($Config['rootCertPath'], $Config['middleCertPath']));
		if($result === FALSE){
			WLog('unionpay_log',"validate signPubKeyCert by rootCert failed");
			return false;
		} else if($result === TRUE){
			CertUtil::$verifyCerts510[$certBase64String] = $certBase64String;
    		return CertUtil::$verifyCerts510[$certBase64String];
		} else {
			WLog('unionpay_log',"validate signPubKeyCert by rootCert failed with error");
			return false;
		}
    }
    
    public static function getIdentitiesFromCertficate($certInfo){
    	
    	$cn = $certInfo['subject'];
    	$cn = $cn['CN'];  	
    	$company = explode('@',$cn);
    	
    	if(count($company) < 3) {
    		return null;
    	} 
    	return $company[2];
    }
    
    public static function getEncryptCertId($cert_path=null){
    	
        if(!array_key_exists($cert_path, CertUtil::$encryptCerts)){
            self::initEncryptCert($cert_path);
        }
        if(array_key_exists($cert_path, CertUtil::$encryptCerts)){
        	return CertUtil::$encryptCerts[$cert_path] -> certId;
        }
        return false;
    }

    public static function getEncryptKey($cert_path=null){
        if(!array_key_exists($cert_path, CertUtil::$encryptCerts)){
            self::initEncryptCert($cert_path);
        }
        if(array_key_exists($cert_path, CertUtil::$encryptCerts)){
        	return CertUtil::$encryptCerts[$cert_path] -> key;
        }
        return false;
    }

    private static function initVerifyCerts($cert_dir=null) {
		
        $handle = opendir ( $cert_dir );
        if (!$handle) {
        	WLog('unionpay_log', '证书目录 ' . $cert_dir . '不正确' );
            return false;
        }
        
        while ($file = readdir($handle)) {
            clearstatcache();
            $filePath = $cert_dir . '/' . $file;
            if (is_file($filePath)) {
                if (pathinfo($file, PATHINFO_EXTENSION) == 'cer') {
                	
                    $x509data = file_get_contents($filePath);
			        if($x509data === false ){
			        	
			        	WLog('unionpay_log', " file_get_contents fail。");
			        	
                    	continue;
			        }
                    if(!openssl_x509_read($x509data)){
                    	WLog('unionpay_log',  $certPath ." openssl_x509_read fail。");
                    	continue;
                    }
                    
                    $cert = new Cert();
                    $certdata = openssl_x509_parse($x509data);
                    $cert->certId = $certdata ['serialNumber'];

                    $cert->key = $x509data;
                    CertUtil::$verifyCerts[$cert->certId] = $cert;
                    
                    WLog('unionpay_log', $filePath . "读取成功，序列号：" . $cert->certId);
                }
            }
        }
        closedir ( $handle );
    }

    public static function getVerifyCertByCertId($certId){

        if(count(CertUtil::$verifyCerts) == 0){
            self::initVerifyCerts();
        }
        if(count(CertUtil::$verifyCerts) == 0){
        	WLog('unionpay_log',"未读取到任何证书……");
            return null;
        }
        if(array_key_exists($certId, CertUtil::$verifyCerts)){
            return CertUtil::$verifyCerts[$certId]->key;
        } else {
        	WLog('unionpay_log',"未匹配到序列号为[" . certId . "]的证书");
            return null;
        }
    }
    
	/**
	 * 验签
	 * @param $params 应答数组
	 * @return 是否成功
	 */
	public static function callback_validate($params) {
		$isSuccess = false;

		if($params['signMethod']=='01')
		{
			$signature_str = $params ['signature'];
			unset ( $params ['signature'] );
			// $params_str = $this->createLinkString ( $params, true, false );
			
			if($params['version']=='5.1.0'){

				$strCert = $params['signPubKeyCert'];
				$strCert = CertUtil::verifyAndGetVerifyCert($strCert);
				if($strCert == null){
					WLog('unionpay_log',"validate cert err: " + $params["signPubKeyCert"]);
					$isSuccess = false;
				} else {
					$params_sha256x16 = hash('sha256', $params_str);
					$logger->LogInfo ( 'sha256>' . $params_sha256x16 );
					$signature = base64_decode ( $signature_str );
					$isSuccess = openssl_verify ( $params_sha256x16, $signature,$strCert, "sha256" );
					
					WLog('unionpay_log',$isSuccess ? '验签成功' : '验签失败' );
				}

			} else {
				WLog('unionpay_log',"wrong version: " + $params['version'] );
				$isSuccess = false;
			}
		}
		return $isSuccess;
	}
	        
}
?>